001    /*
002     * @(#)CanvasWC.java     1.0 06/11/1999
003     */
004    
005    package wc;
006    
007    import java.awt.*;
008    import java.awt.geom.*;
009    import java.awt.event.*;
010    import java.awt.image.*;
011    import java.awt.font.*;
012    import javax.swing.JPanel;
013    
014    /**
015     * This <code>CanvasWC</code> class extends the {@link JPanel} class to 
016     * provide control over displaying using a device-independent coordinate 
017     * system called World Coordinates (WC), which is used by applications.
018     * Displaying is buffered to a {@link BufferedImage} object (offscreen 
019     * image) to emulate double buffering rendering. The <code>BufferedImage</code> 
020     * object contains a {@link Graphics2D} object as part of its rendering state.
021     * The <code>Graphics2D</code> object contains an {@link AffineTransform} 
022     * object that defines how to convert coordinates from client space (world 
023     * coordinates) to device-dependent (raster) coordinates in device space.
024     * <p>
025     * Clients need to extend this class, by creating subclasses, in order to 
026     * use it.
027     * <p>
028     * The vertical Y axis of the <code>CanvasWC</code> object is set by default 
029     * in downward direction, but clients may specify it in upward direction 
030     * when the canvas is created.
031     * <p>
032     * ===================
033     * @see        javax.swing.JPanel
034     * @see        java.awt.Graphics2D
035     * @see        java.awt.image.BufferedImage
036     * @see        java.awt.event.MouseListener
037     * @see        java.awt.event.MouseMotionListener
038     * @version    1.1 02/08/2000
039     * @author     Luiz Fernando Martha
040     */
041    // Change history:  Luiz Fernando Martha 17/02/2000:
042    //   Added drawText and setTextAlignment methods.
043    // Change history:  Luiz Fernando Martha 04/04/2000:
044    //   Added setTextOpacity method.
045    // Change history:  Luiz Fernando Martha 02/08/2000:
046    //   Replaced super-class of CanvasWC from Canvas to
047    //   JPanel.  This was done because class Canvas 
048    //   is an AWT class and JPanel is a Swing class.
049    //   When CanvasWC was an extension of Canvas, some  
050    //   conflicts with other Swing UI objects were occuring,
051    //   such as a dropdown object was drawn on the back 
052    //   of the canvas instead of on the front of it.
053    //   Commented out method paint.  Replaced method 
054    //   update by method paintComponet (with the same 
055    //   Graphics g argument).
056    
057    public abstract class CanvasWC extends JPanel implements MouseListener, MouseMotionListener
058    {
059     /**
060      * Defines the canvas raster area. This area corresponds to the sizes of the 
061      * canvas in pixels.
062      * @serial
063      * @see java.awt.Dimension
064      */
065     protected Dimension raster_area;
066    
067     /**
068      * Defines the canvas world coordinate area. This area corresponds to the 
069      * window in client space which is visible in the canvas.  This window is
070      * adjusted to have the same aspect ratio between height and width of the
071      * canvas (in its current state).  When the client specifies the windows,
072      * the adjustment maintains the center of given (raw) window in the same 
073      * position.  This adjustment may be affected by the canvas vertical 
074      * distortion factor. 
075      * @serial
076      * @see java.awt.geom.Rectangle2D.Double
077      * @see #vdistortion
078      * @see #setWorld
079      */
080     protected Rectangle2D.Double world_area;
081    
082     /**
083      * Defines the original canvas world coordinate area. This area corresponds 
084      * to the window in world coordinates which was set by the client.  That is,
085      * it corresponds to the world area prior to adjustment with respect to the 
086      * canvas aspect ratio between height and width. 
087      * @serial
088      * @see #setWorld
089      * @see #world_area
090      */
091     protected Rectangle2D.Double world_raw;
092    
093     /**
094       * Field constant used to define the vertical Y axis of the canvas in 
095       * downward direction.
096       */
097     public static final int DOWNWARD_Y = 0;
098    
099     /**
100       * Field constant used to define the vertical Y axis of the canvas in 
101       * upward direction.
102       */
103     public static final int UPWARD_Y = 1;
104    
105     /**
106      * Keeps the vertical Y axis direction state.  The possible values are 
107      * <code>DOWNWARD_Y</code> (default) or <code>UPWARD_Y</code>.  The 
108      * Y axis direction is specified during creation, when the
109      * <code>CanvasWC</code> constructor is called.
110      * @serial
111      */
112     protected int y_dir;
113    
114     /**
115      * BufferedImage object that is used to implement double
116      * buffering rendering.  This object defines a Graphics2D 
117      * graphics context object used to convert from world coordinates 
118      * to raster coordinates.
119      * @serial
120      */
121     private BufferedImage ioff;
122    
123     /**
124      * Graphics context object (Graphics) of the offscreen
125      * image used privately for double buffering.
126      * @serial
127      */
128     private Graphics goff;
129    
130     /**
131      * Graphics context object (Graphics2D) that contains an 
132      * AffineTransform object that defines how to convert 
133      * coordinates from client space (world coordinates) to device-dependent 
134      * (raster) coordinates in device space.
135      * @serial
136      */
137     private Graphics2D g2off;
138    
139     /**
140      * Keeps the WC window vertical distortion factor.  This factor specifies
141      * the amount of distortion between the height and width of the world
142      * coordinate area. It is essentially a factor between the vertical and
143      * horizontal scales in client world coordinate space. A factor with 
144      * value greater than one will cause a vertical stretching of the 
145      * image.
146      * @serial
147      * @see #setVDistortion
148      * @see #world_area
149      */
150     protected double vdistortion;
151    
152     /**
153      * Private field that holds a auxiliary point in world coordinates.
154      * @serial
155      */
156     private Point2D.Double pt_world;
157    
158     /**
159      * Private field that holds a auxiliary point in raster (device) coordinates.
160      * @serial
161      */
162     private Point pt_raster;
163    
164     /**
165      * Private field that holds a mouse event point in world coordinates.
166      * @serial
167      */
168     private Point2D.Double ept_world;
169    
170     /**
171      * Private field that holds a mouse event point in raster (device) coordinates.
172      * @serial
173      */
174     private Point ept_raster;
175    
176     /**
177       * Field constant used to define transparent text background opacity.
178       */
179     public static final int TRANSPARENT = 0;
180    
181     /**
182       * Field constant used to define opaque text background opacity.
183       */
184     public static final int OPAQUE = 1;
185    
186     /**
187      * Private field that holds current text alignment parameter.
188      */
189     private int text_opacity = TRANSPARENT;
190    
191     /**
192      * Private field that holds text box for opaque displaying.
193      */
194     private Rectangle text_box;
195    
196     /**
197       * Field constant used to define text alignment.
198       */
199     public static final int NORTH_WEST = 0;
200    
201     /**
202       * Field constant used to define text alignment.
203       */
204     public static final int NORTH = 1;
205    
206     /**
207       * Field constant used to define text alignment.
208       */
209     public static final int NORTH_EAST = 2;
210    
211     /**
212       * Field constant used to define text alignment.
213       */
214     public static final int SOUTH_WEST = 3;
215    
216     /**
217       * Field constant used to define text alignment.
218       */
219     public static final int SOUTH = 4;
220    
221     /**
222       * Field constant used to define text alignment.
223       */
224     public static final int SOUTH_EAST = 5;
225    
226     /**
227       * Field constant used to define text alignment.
228       */
229     public static final int WEST = 6;
230    
231     /**
232       * Field constant used to define text alignment.
233       */
234     public static final int CENTER = 7;
235    
236     /**
237       * Field constant used to define text alignment.
238       */
239     public static final int EAST = 8;
240    
241     /**
242       * Field constant used to define text alignment.
243       */
244     public static final int BASE_LEFT = 9;
245    
246     /**
247       * Field constant used to define text alignment.
248       */
249     public static final int BASE_CENTER = 10;
250    
251     /**
252       * Field constant used to define text alignment.
253       */
254     public static final int BASE_RIGHT = 11;
255    
256     /**
257      * Private field that holds current text alignment parameter.
258      */
259     private int text_alignment = BASE_LEFT;
260    
261     /**
262      * Private field that holds current font that is used to display text.
263      */
264     private Font fnt;
265    
266     /**
267      * Font renderer context: private field that is a container for 
268      * the information needed to measure text that is displayed.
269      */
270     private FontRenderContext frc;
271    
272     /**
273      * Private field that holds an auxiliary identity affine 
274      * transformation matrix.
275      */
276      AffineTransform identity;
277    
278     /**
279      * Default constructor.  Sets up vertical Y axis in downward direction.
280      */
281     public CanvasWC()
282     {
283      this( DOWNWARD_Y );
284     }
285    
286     /**
287      * Constructor that sets up WC canvas according to a given orientation.
288      * @param y_dir can either be <code>DOWNWARD_Y</code> or <code>UPWARD_Y</code>
289      * @see #DOWNWARD_Y
290      * @see #UPWARD_Y
291      */
292     public CanvasWC( int y_dir )
293     {
294      this.y_dir = y_dir;
295      vdistortion = 1.0;
296      pt_world = new Point2D.Double();
297      pt_raster = new Point();
298      ept_world = new Point2D.Double();
299      ept_raster = new Point();
300      addMouseMotionListener( this );
301      addMouseListener( this );
302    
303      text_box = new Rectangle();
304    
305      // Create default container of font size information
306      identity = new AffineTransform();
307      frc = new FontRenderContext( identity, false, false );
308     }
309    
310     /**
311      * Redisplay abstract method.  Client's concrete class must implement it.
312      * This method is called every time the canvas is updated.
313      * @param g is the 2D graphics context that the client uses to display
314      */
315     public abstract void redraw( Graphics2D g );
316    
317     /**
318      * Mouse button pressed abstract method.  Client's concrete class must 
319      * implement it.  This method is called when the user presses a mouse 
320      * button in the canvas.
321      * @param e is mouse event object
322      * @param pt is the mouse position in client's floating point world coordinates
323      */
324     public abstract void mousePressedWC( MouseEvent e, Point2D.Double pt );
325    
326     /**
327      * Mouse button dragged abstract method.  Client's concrete class must 
328      * implement it.  This method is called when user drags the mouse in the 
329      * canvas with a button pressed.
330      * @param e is mouse event object
331      * @param pt is the mouse position in client's floating point world coordinates
332      */
333     public abstract void mouseDraggedWC ( MouseEvent e, Point2D.Double pt );
334    
335     /**
336      * Mouse button released abstract method.  Client's concrete class must 
337      * implement it.  This method is called when user the mouse button at the
338      * end of a mouse pressed-dragged-released interaction.
339      * @param e is mouse event object
340      * @param pt is the mouse position in client's floating point world coordinates
341      */
342     public abstract void mouseReleasedWC( MouseEvent e, Point2D.Double pt );
343    
344     /**
345      * Creates the BufferedImage object that is used to implement double
346      * buffering rendering.  This object defines a Graphics2D graphics 
347      * context object used to convert from world coordinates 
348      * to raster coordinates.
349      */
350     private void buildOffscreen()
351     {
352      Dimension dim = getSize();
353      int w = dim.width;
354      int h = dim.height;
355      raster_area = new Dimension( w, h );
356      Image img = createImage( w, h );
357      goff = img.getGraphics();
358      ioff = (BufferedImage)img;
359      if( g2off != null ) g2off.dispose();
360      g2off = ioff.createGraphics();
361      if( fnt != null )
362       g2off.setFont( fnt );
363     }
364    
365     /**
366      * Adjusts the window in world coordinates (world_area) to have the 
367      * same aspect ratio between height and width of the canvas (in its 
368      * current state).
369      * The adjustment maintains the center of given (raw) window in the same 
370      * position.  This adjustment may be affected by the canvas vertical 
371      * distortion factor (vdistortion).
372      */
373     private void adjustWorld()
374     {
375      double  vpr;               // canvas viewport aspect ratio
376      double  xc, yc;            // center of world area
377    
378      vpr = (double)raster_area.height / (double)raster_area.width;
379      xc = world_area.x + (world_area.width * 0.5);
380      yc = world_area.y + (world_area.height * 0.5);
381      if( (world_area.height*vdistortion) > (vpr*world_area.width) )
382       world_area.width = (world_area.height*vdistortion) / vpr;
383      else
384       world_area.height = (world_area.width/vdistortion) * vpr;
385      world_area.x = xc - (world_area.width * 0.5);
386      world_area.y = yc - (world_area.height * 0.5);
387    
388      double   sx, sy, tx, ty;    // WC to raster affine transf. parameters
389      sx = (double)raster_area.width / world_area.width;
390      sy = (double)raster_area.height / world_area.height;
391      tx = - (sx * (double)world_area.x);
392      ty = - (sy * (double)world_area.y);
393    
394      AffineTransform wc2rc = new AffineTransform();
395      if( y_dir == UPWARD_Y )
396       wc2rc.setTransform( sx, 0.0, 0.0, -sy, tx, raster_area.height - ty );
397      else
398       wc2rc.setTransform( sx, 0.0, 0.0, sy, tx, ty );
399      g2off.setTransform( wc2rc );
400     }
401    
402     /**
403      * Sets the limits of the world coordinate window for the target canvas.
404      * The limits are adjusted to be consistent with the canvas
405      * viewport aspect ratio and with the canvas vertical distortion 
406      * factor.  The adjustment is such that the center of the given
407      * window is maintained.
408      * <p>
409      * The canvas is not automatically repainted by this method.
410      * @param xmin is the left limit of target window
411      * @param xmax is the right limit of target window
412      * @param ymin is the bottom limit of target window if vertical Y axis is upward, otherwise it is the top limit
413      * @param ymax is the top limit of target window if vertical Y axis is upward, otherwise it is the bottom limit
414      * @see #world_area
415      * @see #setVDistortion
416      */
417     public void setWorld( double xmin, double xmax, double ymin, double ymax )
418     {
419      if( world_area == null )
420      {
421       world_raw = new Rectangle2D.Double();
422       world_area = new Rectangle2D.Double();
423      }
424      world_raw.x = world_area.x = xmin;
425      world_raw.y = world_area.y = ymin;
426      world_raw.width = world_area.width = xmax - xmin;
427      world_raw.height = world_area.height = ymax - ymin;
428      if( g2off != null )
429       adjustWorld();
430     }
431    
432     /**
433      * Translates the world coordinate window of the target canvas in the 
434      * horizontal direction based on a given pan factor.
435      * This factor is a percentage of the current window horizontal 
436      * size.  A negative factor indicates a window translation to 
437      * the right (the client model will move left), and a positive 
438      * factor indicates a window translation to the left (the client
439      * model will move right).
440      * <p>
441      * The canvas is not automatically repainted by this method.
442      * @param fac is the pan factor
443      * @see #world_area
444      * @see #setWorld
445      */
446     public void panWorldX( double fac )
447     {
448      double translation;            // window horizontal translation
449      double xmin, xmax, ymin, ymax; // new window limits
450    
451      translation = world_area.width * fac;
452      xmin = world_area.x - translation;
453      xmax = world_area.x + world_area.width - translation;
454      ymin = world_area.y;
455      ymax = world_area.y + world_area.height;
456      setWorld( xmin, xmax, ymin, ymax );
457     }
458    
459     /**
460      * Translates the world coordinate window of the target canvas in the 
461      * vertical direction based on a given pan factor.
462      * This factor is a percentage of the current window horizontal 
463      * size. If the vertical Y axis is upward, a negative factor indicates
464      * a upward window translation (the client model will move downward), 
465      * and a positive factor indicates a downward window translation (the 
466      * client model will move upward).  If the vertical Y axis is downward,
467      * the reverse will occur.
468      * <p>
469      * The canvas is not automatically repainted by this method.
470      * @param fac is the pan factor
471      * @see #world_area
472      * @see #setWorld
473      */
474     public void panWorldY( double fac )
475     {
476      double translation;            // window vertical translation
477      double xmin, xmax, ymin, ymax; // new window limits
478    
479      translation = world_area.height * fac;
480      xmin = world_area.x;
481      xmax = world_area.x + world_area.width;
482      ymin = world_area.y - translation;
483      ymax = world_area.y + world_area.height - translation;
484      setWorld( xmin, xmax, ymin, ymax );
485     }
486    
487     /**
488      * Scales the world coordinate window of the target canvas based on the 
489      * given scaling factor. This factor is a percentage of decrease 
490      * (positive factor) or increase (negative factor) of the window sizes.
491      * The result of this inversion (positive ==> decrease, negative ==>
492      * increase) is that the size of the client's enviroment 
493      * decreases with a negative factor and increases with a positive
494      * factor.
495      * <p>
496      * The canvas is not automatically repainted by this method.
497      * @param fac is the scaling factor
498      * @see #world_area
499      * @see #setWorld
500      */
501     public void scaleWorld( double fac )
502     {
503      double scalefac = 1.0 + fac;   // scale factor from percentage factor
504      double xc, yc;                 // center of world area
505      double sizex, sizey;           // world area new half sizes
506    
507      xc = world_area.x + (world_area.width * 0.5);
508      yc = world_area.y + (world_area.height * 0.5);
509      sizex = (world_area.width*0.5) / scalefac;
510      sizey = (world_area.height*0.5) / scalefac;
511      setWorld( xc - sizex, xc + sizex, yc - sizey, yc + sizey );
512     }
513    
514     /**
515      * Sets the WC window vertical distortion factor.  This factor specifies
516      * the amount of distortion between the height and width of the world
517      * coordinate area. It is essentially a factor between the vertical and
518      * horizontal scales in client world coordinate space. A factor with 
519      * value greater than one will cause a vertical stretching of the 
520      * image.
521      * <p>
522      * The canvas is not automatically repainted by this method.
523      * @param vdistortion is the vertical distortion factor
524      * @see #vdistortion
525      * @see #setWorld
526      */
527     public void setVDistortion( double vdistortion )
528     {
529      if( vdistortion > 0.0 ) this.vdistortion = vdistortion;
530     }
531    
532     /**
533      * Gets the WC window vertical distortion factor.
534      * @return vertical distortion factor
535      * @see #vdistortion
536      * @see #setVDistortion
537      */
538     public double getVDistortion()
539     {
540      return( vdistortion );
541     }
542    
543     /**
544      * Transforms the coordinates of a point given in client space (WC) to
545      * device-dependent (raster) coordinates. The canvas device space has 
546      * its origin in the upper-left corner of the canvas.
547      * The <code>CanvasWC</code> object contains an {@link AffineTransform} 
548      * object that defines how to convert coordinates from client space (world 
549      * coordinates) to device-dependent (raster) coordinates in device space.
550      * Note that, to avoid creating new point objects, the method returns a 
551      * reference to a private object and not a copy of a new point.  If 
552      * needed, clients must clone the returned object.
553      * @param world_x is the x coordinate of given point in client space
554      * @param world_y is the y coordinate of given point in client space
555      * @return point in raster (device) coordinates
556      * @see #setWorld
557      * @see #raster2world
558      */
559     public Point world2raster( double world_x, double world_y )
560     {
561      AffineTransform wc2rc = g2off.getTransform();
562      pt_world.x = world_x;
563      pt_world.y = world_y;
564      wc2rc.transform( pt_world, pt_world );
565      pt_raster.x = (int)pt_world.x;
566      pt_raster.y = (int)pt_world.y;
567      return( pt_raster );
568     }
569    
570     /**
571      * Transforms the coordinates of a point given in device space (raster) to
572      * client space (world coordinates). The canvas device space has 
573      * its origin in the upper-left corner of the canvas.
574      * The <code>CanvasWC</code> object contains an {@link AffineTransform} 
575      * object that defines how to convert coordinates from device-dependent 
576      * (raster) coordinates in device space to client space (world 
577      * coordinates).
578      * Note that, to avoid creating new point objects, the method returns a 
579      * reference to a private object and not a copy of a new point.  If 
580      * needed, clients must clone the returned object.
581      * @param raster_x is the x coordinate of the given point in device space
582      * @param raster_y is the y coordinate of the given point in device space
583      * @return point in world (client) coordinates
584      * @see #setWorld
585      * @see #world2raster
586      */
587     public Point2D.Double raster2world( int raster_x, int raster_y )
588     {
589      AffineTransform wc2rc = g2off.getTransform();
590      pt_world.x = (double)raster_x;
591      pt_world.y = (double)raster_y;
592      try {
593       wc2rc.inverseTransform( pt_world, pt_world );
594      }
595      catch( NoninvertibleTransformException e ) {
596       pt_world.x = 0.0;
597       pt_world.y = 0.0;
598      }
599      return( pt_world );
600     }
601    
602     /**
603      * Handles the event of the user pressing down the mouse button.
604      * Calls client method <code>mousePressedWC</code>
605      * @param e is mouse event object
606      * @see #mousePressedWC
607      */
608     public void mousePressed( MouseEvent e )
609     {
610      AffineTransform wc2rc = g2off.getTransform();
611      ept_world.x = (double)e.getX();
612      ept_world.y = (double)e.getY();
613      try {
614       wc2rc.inverseTransform( ept_world, ept_world );
615      }
616      catch( NoninvertibleTransformException error ) {
617       ept_world.x = 0.0;
618       ept_world.y = 0.0;
619      }
620      mousePressedWC( e, ept_world );
621     }
622    
623     /**
624      * Handles the event of the user dragging the mouse while holding down the mouse button.
625      * Calls client method <code>mouseDraggedWC</code>
626      * @param e is mouse event object
627      * @see #mouseDraggedWC
628      */
629     public void mouseDragged( MouseEvent e )
630     {
631      AffineTransform wc2rc = g2off.getTransform();
632      ept_world.x = (double)e.getX();
633      ept_world.y = (double)e.getY();
634      try {
635       wc2rc.inverseTransform( ept_world, ept_world );
636      }
637      catch( NoninvertibleTransformException error ) {
638       ept_world.x = 0.0;
639       ept_world.y = 0.0;
640      }
641      mouseDraggedWC( e, ept_world );
642     }
643    
644     /**
645      * Handles the event of the user releasing the mouse button.
646      * Calls client method <code>mouseReleasedWC</code>
647      * @param e is mouse event object
648      * @see #mouseReleasedWC  */
649     public void mouseReleased( MouseEvent e )
650     {
651      AffineTransform wc2rc = g2off.getTransform();
652      ept_world.x = (double)e.getX();
653      ept_world.y = (double)e.getY();
654      try {
655       wc2rc.inverseTransform( ept_world, ept_world );
656      }
657      catch( NoninvertibleTransformException error ) {
658       ept_world.x = 0.0;
659       ept_world.y = 0.0;
660      }
661      mouseReleasedWC( e, ept_world );
662     }
663    
664     // This method required by MouseListener.
665     /**
666      * Dummy method for the event of mouse motion in canvas.
667      * @param e is mouse event object
668      */
669     public void mouseMoved( MouseEvent e ){}
670    
671     // This method is required by MouseMotionListener.
672     /**
673      * Dummy method for the event of mouse clicked in canvas.
674      * @param e is mouse event object
675      */
676     public void mouseClicked( MouseEvent e ){}
677    
678     // This method is required by MouseMotionListener.
679     /**
680      * Dummy method for the event of mouse exited from canvas.
681      * @param e is mouse event object
682      */
683     public void mouseExited( MouseEvent e ){}
684    
685     // This method is required by MouseMotionListener.
686     /**
687      * Dummy method for the event of mouse entered in canvas.
688      * @param e is mouse event object
689      */
690     public void mouseEntered( MouseEvent e ){}
691    
692     /**
693      * Repaint method of canvas. It calls method <code>update</code>.
694      * @param g is the target graphics context
695      * @see #update
696      */
697    // public void paint( Graphics g )
698    // {
699    //  update( g );
700    // }
701    
702     /**
703      * Update method of canvas.
704      * <br>
705      * In case the sizes of the canvas have changed, it (re)builds a 
706      * {@link BufferedImage} object (offscreen image) to implement
707      * double buffering rendering.  In this case, the world coordinate window 
708      * defined by the client (if defined) is adjusted according to the new canvas
709      * sizes.
710      * <br>
711      * Then, it clears the canvas using the currently defined background color and
712      * calls the client's redraw method, passing the {@link Graphics2D} graphics 
713      * context object of the offscreen image, so that the client may display
714      * on it.
715      * <br>
716      * Finally, it fills the canvas with the image that was displayed by the canvas
717      * on the offscreen image.      
718      * @param g is the target graphics context
719      * @see #setWorld
720      * @see #redraw
721      */
722     public void paintComponent( Graphics g )
723     {
724      if( (raster_area == null) || !raster_area.equals(getSize()) )
725      {
726       buildOffscreen();
727       if( world_area == null )
728       {
729        world_area = new Rectangle2D.Double( 0.0, 0.0, 
730                    (double)raster_area.width, (double)raster_area.height );
731        world_raw = (Rectangle2D.Double)world_area.clone();
732        adjustWorld();
733       }
734       else
735       {
736        world_area.x = world_raw.x;
737        world_area.y = world_raw.y;
738        world_area.width = world_raw.width;
739        world_area.height = world_raw.height;
740        adjustWorld();
741       }
742      }
743    
744      // Clears the offscreen area.
745      g2off.setColor( getBackground() );
746      g2off.fill( world_area );
747    
748      // Calls abstract method for redrawing world.
749      redraw( g2off );
750    
751      // Draws the buffered image to the screen.
752      g.drawImage( ioff, 0, 0, this );
753     }
754    
755     /**
756      * Set font to be used to display texts.
757      */
758     public void setFont( Font fnt )
759     {
760      this.fnt = fnt;
761      if( g2off != null )
762       g2off.setFont( fnt );
763     }
764    
765     /**
766      * Set opacity to be used to display texts.
767      */
768     public void setTextOpacity( int text_opacity )
769     {
770      this.text_opacity = text_opacity;
771     }
772    
773     /**
774      * Set alignment to be used to display texts.
775      */
776     public void setTextAlignment( int text_alignment )
777     {
778      this.text_alignment = text_alignment;
779     }
780    
781     /**
782      * Draw a given string on a given position. 
783      * (Parts of this method were stolen from TWFText.java)
784      */
785     public void drawString( String text, double x, double y )
786     {
787      TextLayout text_layout = new TextLayout( text, fnt, frc );
788      Color curr_color = g2off.getColor();
789      int w  = (int)text_layout.getAdvance();
790      int as = (int)text_layout.getAscent();
791      int ds = (int)text_layout.getDescent();
792      int dx = 0;
793      int dy = 0;
794    
795      switch( text_alignment )
796      {
797       case NORTH_WEST:
798        dy -= as;
799        break;
800       case NORTH:
801        dx -= w/2;
802        dy -= as;
803        break;
804       case NORTH_EAST:
805        dx -= w;
806        dy -= as;
807        break;
808       case SOUTH_WEST:
809        dy += ds;
810        break;
811       case SOUTH:
812        dx -= w/2;
813        dy += ds;
814        break;
815       case SOUTH_EAST:
816        dx -= w;
817        dy += ds;
818       break;
819      case WEST:
820        dy -= (as - ds)/2;
821        /* Deduction:
822         *                _Top      _
823         *    AAAA                   |
824         *   A    A                  | AS
825         *   AAAAAA pppp             |
826         *   A    A p   p            |
827         *   A    A pppp  -BaseLine  -
828         *          p                | DS
829         *          p     _Bottom   _|
830         *
831         * AS is the ascent and DS is the descent of
832         * the text.  To center the text in vertical  
833         * direction we need to shift upward the 
834         * baseline by AS, and downward by (AS+DS)/2.
835         * So: dy = AS - (AS+DS)/2 = (AS-DS)/2.
836         */
837        break;
838       case CENTER:
839        dx -= w/ 2;
840        dy -= (as - ds)/2;
841        // look at "west" case
842        break;
843       case EAST:
844        dx -= w;
845        dy -= (as - ds)/2;
846        // look at "west" case
847        break;
848       default:
849       case BASE_LEFT:
850        //dx = 0.0;
851        //dy = 0.0; // trivial
852        break;
853       case BASE_CENTER:
854        dx -= w/2;
855        break;
856       case BASE_RIGHT:
857        dx -= w;
858        break;
859      }
860    
861      /* Finally draw the given text string. This is a little bit
862       * tricky: The (Graphics2D) graphics context will transform
863       * the text string with its current affine transformation 
864       * matrix prior to displaying it.  To get the text with
865       * its original font size, we temporarely set the current
866       * transformation matrix to the identity matrix, draw the
867       * text in raster coordinates, and restore the correct
868       * affine transformation matrix.
869       */
870      pt_raster = world2raster( x, y );
871      AffineTransform g2off_transf = g2off.getTransform();
872      g2off.setTransform( identity );
873      if( text_opacity == OPAQUE )
874      {
875       text_box.x = pt_raster.x + dx - 4;
876       text_box.y = pt_raster.y - dy - as;
877       text_box.width  = w + 7;
878       text_box.height = as + ds;
879       g2off.setColor( getBackground() );
880       g2off.fill( text_box );
881       g2off.setColor( curr_color );
882      }
883      g2off.drawString( text, pt_raster.x + dx, pt_raster.y - dy );
884      g2off.setTransform( g2off_transf );
885     }
886    }